package org.javaboy.redisson_demo.service;


import jakarta.annotation.Resource;
import org.redisson.RedissonMultiLock;
import org.redisson.RedissonRedLock;
import org.redisson.api.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;

import java.util.concurrent.TimeUnit;

@Service
public class LockService {
    //因为有一个 RedissonClient 上有 @Primary 注解，这里注入的就是该 Bean
    @Autowired
    RedissonClient client;

    //    @Resource(name = "redis6379")
    @Autowired
    @Qualifier("redis6379")
    RedissonClient redis6379;
//    @Autowired
//    @Qualifier("redis6380")
//    @Resource(name = "redis6380")
    RedissonClient redis6380;
    String lockName = "javaboy_lock";

    public void countDownLatch() throws InterruptedException {
        RCountDownLatch countDownLatch = client.getCountDownLatch(lockName);
        countDownLatch.trySetCount(10);
        //现在有工作需要 10 个线程并发执行
        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                try {
                    System.out.println(Thread.currentThread().getName() + " 任务执行中");
                    Thread.sleep(1000);
                    //相当于 -1
                    countDownLatch.countDown();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }).start();
        }
        //等待，等待 10 变为 0，然后继续执行后续的代码
        countDownLatch.await();
        System.out.println("OK");
    }

    public void expireSemaphore() {
        RPermitExpirableSemaphore semaphore = client.getPermitExpirableSemaphore(lockName);
        //设置信号量为 10，每个线程最多只能获取 1 个信号量
        //相当于线程池有 10 个线程
        semaphore.trySetPermits(30);
        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                //获取信号量，获取 1 个信号量
//                boolean isLock = semaphore.tryAcquire();
                try {
                    //这个相当于是信号
                    String permitId = semaphore.tryAcquire(10, 10, TimeUnit.SECONDS);
                    System.out.println("permitId = " + permitId);
                    //释放信号量，释放 1 个信号量，需要指定具体要释放的信号是哪个
                    semaphore.release(permitId);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }

            }).start();
        }
    }

    /**
     * 信号量
     */
    public void semaphore() {
        RSemaphore semaphore = client.getSemaphore(lockName);
        //设置信号量为 10，每个线程最多只能获取 1 个信号量
        //相当于线程池有 10 个线程
        semaphore.trySetPermits(30);
        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                //获取信号量，获取 1 个信号量
                boolean isLock = semaphore.tryAcquire();
                //获取信号量，获取 10 个信号量
//                boolean isLock = semaphore.tryAcquire(10);
                if (isLock) {
                    //获取信号量成功，执行业务代码
                    System.out.println(Thread.currentThread().getName() + " 获取信号量成功");
                    //释放信号量，释放 1 个信号量
                    semaphore.release();
                    //释放 10 个信号量
//                    semaphore.release(10);
                } else {
                    System.out.println(Thread.currentThread().getName() + " 获取信号量失败");
                }
            }).start();
        }
    }

    public void readWriteLock() {
        RReadWriteLock lock = client.getReadWriteLock(lockName);
        //读读共享
        RLock rLock = lock.writeLock();
        try {
            boolean isLock = rLock.tryLock();
            if (isLock) {
                System.out.println(Thread.currentThread().getName() + " 获取锁成功");
            } else {
                System.out.println(Thread.currentThread().getName() + " 获取锁失败");
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            if (rLock.isLocked() && rLock.isHeldByCurrentThread()) {
                //释放锁
                rLock.unlock();
            }
        }
    }

    public void redLock() {
        RLock lock6379 = redis6379.getLock(lockName);
        RLock lock6380 = redis6380.getLock(lockName);
        //构建一个联锁
        RedissonRedLock redLock = new RedissonRedLock(lock6379, lock6380);
        try {
            boolean isLock = redLock.tryLock();
            if (isLock) {
                System.out.println(Thread.currentThread().getName() + " 获取锁成功");
            } else {
                System.out.println(Thread.currentThread().getName() + " 获取锁失败");
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            if (redLock.isHeldByCurrentThread()) {
                //释放锁
                redLock.unlock();
            }
        }
    }

    public void multiLock() {
        RLock lock6379 = redis6379.getLock(lockName);
        RLock lock6380 = redis6380.getLock(lockName);
        //构建一个联锁
        RedissonMultiLock multiLock = new RedissonMultiLock(lock6379, lock6380);
        try {
            boolean isLock = multiLock.tryLock();
            if (isLock) {
                System.out.println(Thread.currentThread().getName() + " 获取锁成功");
            } else {
                System.out.println(Thread.currentThread().getName() + " 获取锁失败");
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            if (multiLock.isHeldByCurrentThread()) {
                //释放锁
                multiLock.unlock();
            }
        }
    }

    /**
     * 公平锁
     * 公平锁是指多个线程按照申请锁的顺序来获取锁，性能比较差，但是可以保证锁的顺序。
     * 底层使用的是 Redis 的有序集合，通过 ZADD 命令将锁按照线程的 id 排序。
     */
    public void fairLock() {
        //获取公平锁
        RLock lock = client.getFairLock(lockName);
        try {
            //获取锁的等待时间，即当前线程前来获取锁，但是锁被其他线程持有，那么当前线程会等待一段时间，
            long waitTime = 3;
            //锁的有效期，这里设置为 1s，超时之后，锁就自动释放了
            //leaseTime 是锁的有效期，但是用户有可能在锁到期之前手动将锁释放掉
            long leaseTime = 1;
            boolean isLock = lock.tryLock(waitTime, leaseTime, TimeUnit.SECONDS);
            if (isLock) {
                Thread.sleep(1000);
                System.out.println(Thread.currentThread().getName() + " 获取锁成功");
            } else {
                System.out.println(Thread.currentThread().getName() + " 获取锁失败");
            }
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        } finally {
            if (lock.isLocked() && lock.isHeldByCurrentThread()) {
                //释放锁
                lock.unlock();
            }
        }
    }


    public void lockNoParams() {
        RLock lock = client.getLock(lockName);
        try {
            //如果获取不到锁，代码就会阻塞在这里，直到获取到锁为止
            lock.lock();
            System.out.println(Thread.currentThread().getName() + " 获取锁成功");
        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            if (lock.isLocked() && lock.isHeldByCurrentThread()) {
                //释放锁
                lock.unlock();
            }
        }
    }

    /**
     * 对于有参的 tryLock，不存在看门狗机制，因为 waitTime 和 leaseTime 都是自己设置的
     * <p>
     * 这个带参数的 tryLock，底层使用了发布订阅机制。
     * 当锁被释放的时候（lock.unlock();），会发布一个消息，告诉其他线程，当前这个锁释放了，你们可以来抢了。所有订阅了该锁的线程都会收到这个消息。
     * <p>
     * 以下面这个为例，一共来了 10 个线程，只有一个线程能抢到锁，其他线程都会被阻塞。但是其他线程在阻塞的时候，会订阅一个消息。当抢到锁的线程执行完毕，释放锁的时候（lock.unlock();），他会发送一条消息出来，告诉其他线程，这个锁释放了，其他线程都订阅了这条消息，都会收到这个消息。收到消息之后，其他线程都会去抢锁。
     * <p>
     * 但是！！！在无参的 tryLock 里边，是不存在这个发布订阅机制的。
     */
    public void tryLockWithParams() {
        RLock lock = client.getLock(lockName);
        try {
            //获取锁的等待时间，即当前线程前来获取锁，但是锁被其他线程持有，那么当前线程会等待一段时间，
            long waitTime = 3;
            //锁的有效期，这里设置为 1s，超时之后，锁就自动释放了
            //leaseTime 是锁的有效期，但是用户有可能在锁到期之前手动将锁释放掉
            long leaseTime = 1;
            boolean isLock = lock.tryLock(waitTime, leaseTime, TimeUnit.SECONDS);
            if (isLock) {
                Thread.sleep(1 * 1000);
                System.out.println(Thread.currentThread().getName() + " 获取锁成功");
            } else {
                System.out.println(Thread.currentThread().getName() + " 获取锁失败");
            }
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        } finally {
            if (lock.isLocked() && lock.isHeldByCurrentThread()) {
                //释放锁
                lock.unlock();
            }
        }
    }

    /**
     * 这个 tryLock 在加锁的时候，没有设置锁的超时时间，默认有效期是 30s。但是，这里有一个看门狗机制，watch dog，
     * watch dog 其实就是一个定时任务，每隔 10s，会去检查一次锁是否还持有，如果持有，那么这个锁就会自动续期。
     * <p>
     * 对于无参的 tryLock，waitTime 相当于是 0，即不等待，直接获取锁，如果获取不到，就返回 false。
     * leaseTime 默认是 30s，但是会自动续期
     */
    public void tryLockNoParams() {
        //创建锁，注意，此时还没有去获取锁，这个时候拿到手的其实就是一个可重入锁
        //这个锁底层数据结构是 hash，key filed value，value 就是重入的次数
        //key 就是锁名称，这里就是 javaboy_lock
        // filed 是一个随机字符串，这个字符串是 UUID
        // value 是重入次数
        RLock lock = client.getLock(lockName);
        try {
            //获取锁，尝试获取锁
            boolean isLock = lock.tryLock();
            if (isLock) {
                //因为是可重入锁，所以可以重复获取，每次获取都会把重入次数加一
//                lock.tryLock();
//                lock.tryLock();
//                lock.tryLock();
//                lock.tryLock();
//                lock.tryLock();
                System.out.println(Thread.currentThread().getName() + " 获取锁成功");
            } else {
                System.out.println(Thread.currentThread().getName() + " 获取锁失败");
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            //释放锁
            //如果当前处于锁状态，并且这个锁是被当前线程所持有
            if (lock.isLocked() && lock.isHeldByCurrentThread()) {
                lock.unlock();
//                lock.unlock();
//                lock.unlock();
//                lock.unlock();
//                lock.unlock();
//                lock.unlock();
            }
        }
    }
}
